﻿// for GPS Device (GPS52D)

using System;
using System.Collections.Generic;
using System.Text;
using System.IO.Ports;
using System.Threading;

namespace Kakehashi.Device
{
    /// <summary>
    /// センサのステータスを返します
    /// </summary>
    public enum GPS52DState { DISCONNECTED, CONNECTED };

    /// <summary>
    /// 秋月 GPS-52D(B)-014 処理クラス
    /// </summary>
    [CLSCompliantAttribute(true)]
    public class GPS52D : IDisposable
    {
        const string SERIAL_PORT_ERROR = "N/A";

        private SerialPort serialPort_;
        private string portName_ = "";
        private GPS52DState status_ = GPS52DState.DISCONNECTED;

        /// <summary>
        /// デバイスから取得した生データ。複数のスレッドで
        /// 読み書きするので注意。
        /// </summary>
        private string rawData_ = "";

        /// <summary>
        /// 別スレッドでのシリアルポート読み取りを制御するフラグ
        /// </summary>
        private bool _isRunning = false;
        
        /// <summary>
        /// データ読み込みスレッド
        /// </summary>
        private Thread readThread_;
        

        /// <summary>
        /// デバイスの接続状況
        /// </summary>
        public GPS52DState Status { get { return status_; } }

        /// <summary>
        /// デバイス接続先のポート名
        /// </summary>
        public string PortName { get { return portName_; } }

         /// <summary>
        /// 秋月 ＧＰＳモジュール: GPS-52D(B)-014に接続します。
        /// </summary>
        /// <param name="portName">接続先ポート名 "COM2" など</param>
        public GPS52D(string portName)
        {
            ConnectGPS52D(portName);
        }

        /// <summary>
        /// 秋月 ＧＰＳモジュール: GPS-52D(B)-014に接続します。
        /// 接続先は自動的に検出します。
        /// </summary>
        public GPS52D()
        {
            string[] ports = System.IO.Ports.SerialPort.GetPortNames();
            for (int i = 0; i < ports.Length; i++)
            {
                ConnectGPS52D(ports[i]);
                if (status_ == GPS52DState.CONNECTED)
                {
                    return;
                }
            }
        }

        private void ConnectGPS52D(string portName)
        {
            InitSerialPort(portName);
        }

        /// <summary>
        /// デバイスをクローズします。
        /// </summary>
        public void Close()
        {
            _isRunning = false;

            if (readThread_ != null)
            {
                readThread_.Join();
                readThread_ = null;
            }

            if (serialPort_ != null)
            {
                serialPort_.Close();
                serialPort_ = null;
            }
            status_ = GPS52DState.DISCONNECTED;
            portName_ = "";
        }

        /// <summary>
        /// デバイスをDisposeします。内部でClose()を実行します。
        /// </summary>
        public void Dispose()
        {
            Close();
        }

        /// <summary>
        /// シリアルポートを初期化します
        /// </summary>
        /// <param name="portName">ポート名</param>
        private void InitSerialPort(string portName)
        {
            Close();

            portName_ = portName;

            serialPort_ = new SerialPort();
            serialPort_.PortName = portName_;
            serialPort_.BaudRate = 38400; // 9600
            serialPort_.DataBits = 8;
            serialPort_.Parity = System.IO.Ports.Parity.None;
            serialPort_.StopBits = System.IO.Ports.StopBits.One;
            serialPort_.ReadBufferSize = 1024;
            serialPort_.Handshake = System.IO.Ports.Handshake.None;
            serialPort_.Encoding = Encoding.ASCII;
            serialPort_.DtrEnable = true;
            serialPort_.RtsEnable = true;
            serialPort_.NewLine = "\r\n";
            serialPort_.ReadTimeout = 1500;
            serialPort_.WriteTimeout = 1500;
            try
            {

                serialPort_.Open();
                status_ = GPS52DState.CONNECTED;

                _isRunning = true;
                readThread_ = new Thread(Read);
                readThread_.IsBackground = true;
                readThread_.Start();
            }
            catch
            {
                Close();
            }
        }

        /// <summary>
        /// 別スレッドで実行されるデータ受信処理
        /// メインスレッドが _isRunning を false にするまでループ
        /// </summary>
        private void Read()
        {
            string str = "";

            while (_isRunning)
            {
                try
                {
                    str = serialPort_.ReadLine();
                }
                catch (Exception)
                {
                    Close();
                    return;
                }
                if (str.StartsWith("$GPGGA"))
                {
                    lock (this)
                    {
                        rawData_ = str;
                    }
                }// 他の行のデータは無視する
                Thread.Sleep(100);
            }
        }

        /// <summary>
        /// センサから値を取得します
        /// </summary>
        /// <returns>取得データ</returns>
        public GPS52DData GetData()
        {
            GPS52DData data = new GPS52DData();
            data.UTCTime = DateTime.UtcNow.ToFileTimeUtc();

            if (status_ == GPS52DState.DISCONNECTED) return data;

            // データ取得
            string buf;
            lock (this)
            {
                // $GPGGAではじまる行のみ返ってきます
                buf = (string)rawData_.Clone();
            }

            if (buf != null && buf != "")
            {
                string[] str = buf.Split(',');

                // UTC時刻の計算 123456.88 -> 12:34:67.88
                DateTime now = DateTime.UtcNow;
                int hour = Convert.ToInt16(str[1].Substring(0, 2));
                int min  = Convert.ToInt16(str[1].Substring(2, 2));
                int sec  = Convert.ToInt16(str[1].Substring(4, 2));
                int mil  = Convert.ToInt16(str[1].Substring(7, 2));
                DateTime time = new DateTime(now.Year, now.Month, now.Day,
                    hour, min, sec, mil);
                data.UTCTimeGPS = time.ToFileTimeUtc();

                // 緯度計算
                int la0 = Convert.ToInt16(str[2].Substring(0, 2));
                int la1 = Convert.ToInt16(str[2].Substring(2, 2));
                int la2 = Convert.ToInt16(str[2].Substring(5, 4));

                double la = la0 + la1/60.0 + la2/600000.0;

                // 経度計算
                int lo0 = Convert.ToInt16(str[4].Substring(0, 3));
                int lo1 = Convert.ToInt16(str[4].Substring(3, 2));
                int lo2 = Convert.ToInt16(str[4].Substring(6, 4));

                double lo = lo0 + lo1 / 60.0 + lo2 / 600000.0;

                data.Latitude = la;
                data.Longitude = lo;

                if (str[3] != "N") data.Latitude = -data.Latitude;
                if (str[5] != "E") data.Longitude = -data.Longitude;

                data.FixQuality = Convert.ToInt16(str[6]);
                data.SatelliteCount = Convert.ToInt16(str[7]);
                data.DOP = (float)Convert.ToDouble(str[8]);
                data.MeanSeaHeight = (float)Convert.ToDouble(str[9]);
                data.WGS84Height = (float)Convert.ToDouble(str[11]);
            }
            return data;
        }
    }
}
